package calculadora;

import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Arrays;
import java.util.HashMap;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import java.util.Stack;

import utils.Dentre;

import exceptions.OperandException;
import exceptions.UnBalancedExpressionException;


public class LogicParser {
	private Stack <String> myStack;

	private HashMap <Integer, String> operatorMap; 
	private HashMap<Integer,String> operandSet;
	private HashMap <String,Boolean> operandValue;

	public LogicParser (){
		myStack= new Stack<String>();
		createMapFromFile("./tps/LogiCalc/res/operators.dat");
		createOperandSet();


	}
	/**
	 * parses a logical expression into a Linked list containing operators and operands only, removing blank spaces. 
	 * @param expression: a logical expression
	 * @return 
	 */
	public List <String> parse (String expression)throws UnBalancedExpressionException{
		int brackets=0;
		//remove leading or tailing spaces
		expression = expression.trim();
		//create dirty expression list
		List <String> dirtyExpressionList = Arrays.asList(expression.split(" "));
		// create blank-free list
		List<String> expList = new LinkedList<String>();
		for (Iterator <String>iterator = dirtyExpressionList.listIterator(); iterator.hasNext();) {
			String string = (String) iterator.next().toUpperCase();
			if ((operandSet.containsValue(string))||operatorMap.containsValue(string)){
				expList.add(string);
			}
		}
		for (Iterator <String> iterator = expList.listIterator();iterator.hasNext();){

			String string =  iterator.next();
			if (string.equals("(")||string.equals("[")){
				brackets++;
			}
			if (string.equals(")")||string.equals("]")){
				brackets--;
			}

		}
		if (brackets!=0){
				throw new UnBalancedExpressionException ("unbalanced operation");
		}


		return expList;

	}
	/**
	 * createMapFromFile (String path) reads the given file and maps each line to a hash code used as a unique key. 
	 */
	private void createMapFromFile (String path ){
		this.operatorMap= new HashMap <Integer,String>();
		try{ 

			BufferedReader br = new BufferedReader (new FileReader (path));
			String line= br.readLine();
			while (line!=null)	{
				operatorMap.put(line.hashCode(), line);
				line=br.readLine();
			}


		}catch (Exception e){
			e.printStackTrace();
		}

	}
	/**
	 * creates a set that contains the operands allowed
	 */
	private void createOperandSet (  ){
		operandSet= new HashMap<Integer, String>();
		for (char i = 'A'; i <='Z'; i++) {
			if(i!='Y'&&i!='O')
			operandSet.put((""+i).hashCode(),""+i);
		}


	}
	public List<String> toPostFix(List<String> infixExpression)throws UnBalancedExpressionException{
		List<String> postFix=new LinkedList<String>();
		Iterator <String>iterator = infixExpression.listIterator();
		while (iterator.hasNext()){
			try {

				String currentToken =  iterator.next();
				//If the token is an operand we simply add it to our answer.

				if (operandSet.containsValue(currentToken)){
					postFix.add(currentToken);
				}

				//If the token is an operator, and there's nothing else on our stack, we push that operator onto the stack. 
				if (operatorMap.containsValue(currentToken) && myStack.isEmpty()){
					myStack.push(currentToken);

				}else {
					//If the token is a left parenthesis we simply push it onto the stack.
					if (operatorMap.containsValue(currentToken)&& currentToken.equals("(")){
						myStack.push(currentToken);

					}else{//operator is not a left parenthesis
						/**
						 * if the operator is a right parenthesis, we pop all the operators until the first left
						 * parenthesis. 
						 */
						if (currentToken.equals(")")){
							while (!myStack.peek().equals("("))
								postFix.add(myStack.pop());
							myStack.pop();//pop the corresponding "(" 
						}else {
							/**
							 * If there is something on the stack, we compare the precedence of our current 
							 * operator with the one on the stack 
							 */
							if (!myStack.isEmpty()&&operatorMap.containsValue(currentToken)){
								/**
								 * Handle unary operator before the rest of them
								 */

								if (currentToken.equalsIgnoreCase("no")){
									myStack.push(currentToken);

									currentToken=iterator.next();
									postFix.add(currentToken);
									postFix.add(myStack.pop());

								}else
								{
									myStack.push(currentToken);


								}

							}
						}
					}
				}

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		while(! myStack.isEmpty()){
			postFix.add(myStack.pop());

		}
		return postFix;
	}
	/**
	 * creates a map with the corresponding valid operands for the logical expression that the user will use and their logical vales (0 or 1 ) 
	 * @throws OperandException
	 */
	public  void setOperandsInUse ()throws OperandException{
		char letra='A';
		int valor;
		System.out.println("ingrese la cantidad de operandos");
		int operands= Dentre.readInt();
		if (operands<=0||operands>24) {
			throw new OperandException ("invalid operand number");
		}
		operandValue= new HashMap<String, Boolean>();
		for (int i = 0; i < operands; i++) {
			do{
				
			System.out.println("VALOR DE OPERANDO "+letra+" 0 - FALSE 1- TRUE :");
			valor = Dentre.readInt();
			}while (valor!=0 &&valor!=1	);
			if (valor==1){
				operandValue.put(letra+"", new Boolean(true));
			}else{
				
				operandValue.put(letra+"", new Boolean(false));
			}
			letra++;
			
		}
	}
	public boolean calcular (List<String> postfixOperation)throws OperandException,UnBalancedExpressionException{
		Stack<Boolean> myStack = new Stack<Boolean>();
		Iterator <String> iterator = postfixOperation.listIterator();
		String currentToken;
		while (iterator.hasNext()){
			currentToken=iterator.next();
			//if the token is an operand get its value from the map
			if (operandValue.containsKey(currentToken)){
				myStack.add(operandValue.get(currentToken));
				
			}
			// if it is an operator pop the operands needed and push back the result, 
			if (operatorMap.containsValue(currentToken)){
				if (currentToken.equalsIgnoreCase("no")){
					if (myStack.isEmpty()){
						throw new UnBalancedExpressionException ("wrong expression");
						
					}
					else{
					 Boolean temp= myStack.pop();
					 myStack.push(new Boolean(!temp.booleanValue()));
					}
						
				}
				else
				{
					if (myStack.size()<2){
						throw new UnBalancedExpressionException ("missing operands");
					}else {
						if (currentToken.equalsIgnoreCase("y")){
							Boolean q = myStack.pop();
							Boolean p= myStack.pop();
							myStack.push(new Boolean(p.booleanValue()&&q.booleanValue()));
						}
						if (currentToken.equalsIgnoreCase("O")){
							Boolean q = myStack.pop();
							Boolean p= myStack.pop();
							myStack.push(new Boolean(p.booleanValue()||q.booleanValue()));
						}
						if (currentToken.equalsIgnoreCase("entonces")){
							Boolean q = myStack.pop();
							Boolean p= myStack.pop();
							myStack.push(new Boolean(!p.booleanValue()||q.booleanValue()));
						}
						if (currentToken.equalsIgnoreCase("SOLO SI")){
							Boolean q = myStack.pop();
							Boolean p= myStack.pop();
							myStack.push(new Boolean((!p.booleanValue()||q.booleanValue())&&(!q.booleanValue()||p.booleanValue())));
						}
						
					}
					
				}
			}
		}
		return myStack.pop().booleanValue();
	}
	
	
	public static void main (String args[]){
		LogicParser l = new LogicParser();
		try {
			l.setOperandsInUse();
			System.out.println("enter infix operation");
			List<String> infix =l.parse(Dentre.readString());
			
			List<String> postfix=l.toPostFix(infix);
			System.out.println("postfix operation");
			System.out.println(postfix);
			System.out.println(l.calcular(postfix));
			
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
